"""
DragonQuant配置管理模块
"""

import os
import json
import yaml
import logging
from typing import Dict, Any, Optional, Union
from pathlib import Path
from .exceptions import ConfigurationError

logger = logging.getLogger(__name__)


class Config:
    """配置管理类"""
    
    DEFAULT_CONFIG = {
        'backtest': {
            'initial_cash': 1000000.0,
            'commission': 0.0003,
            'stamp_duty': 0.001,
            'min_commission': 5.0,
            'data_source': 'fake',
            'cache_enabled': True,
            'max_position_size': 0.1,
            'max_total_leverage': 1.0
        },
        'data': {
            'data_path': './data',
            'cache_path': './cache',
            'update_frequency': 'daily',
            'retry_times': 3,
            'timeout': 30
        },
        'logging': {
            'level': 'INFO',
            'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s',
            'file_path': './logs/dragonquant.log',
            'max_file_size': 10 * 1024 * 1024,  # 10MB
            'backup_count': 5
        },
        'output': {
            'results_path': './backtest_results',
            'save_charts': True,
            'save_reports': True,
            'chart_format': 'png',
            'chart_dpi': 300
        },
        'performance': {
            'use_multiprocessing': False,
            'max_workers': 4,
            'chunk_size': 1000
        }
    }
    
    def __init__(self, config_file: Optional[str] = None):
        """初始化配置管理器
        
        Args:
            config_file: 配置文件路径，支持.json和.yaml格式
        """
        self._config = self.DEFAULT_CONFIG.copy()
        self._config_file = config_file
        
        # 加载配置文件
        if config_file:
            self.load_from_file(config_file)
        
        # 加载环境变量
        self.load_from_env()
        
        logger.info("Configuration manager initialized")
    
    def load_from_file(self, config_file: str):
        """从文件加载配置"""
        try:
            config_path = Path(config_file)
            
            if not config_path.exists():
                logger.warning(f"Config file not found: {config_file}")
                return
            
            with open(config_path, 'r', encoding='utf-8') as f:
                if config_path.suffix.lower() == '.json':
                    file_config = json.load(f)
                elif config_path.suffix.lower() in ['.yaml', '.yml']:
                    file_config = yaml.safe_load(f)
                else:
                    raise ConfigurationError(f"Unsupported config file format: {config_path.suffix}")
            
            # 深度合并配置
            self._merge_config(self._config, file_config)
            logger.info(f"Configuration loaded from {config_file}")
            
        except Exception as e:
            logger.error(f"Failed to load config from {config_file}: {str(e)}")
            raise ConfigurationError(f"Failed to load config: {str(e)}")
    
    def load_from_env(self):
        """从环境变量加载配置"""
        env_mapping = {
            'DRAGONQUANT_INITIAL_CASH': ('backtest', 'initial_cash', float),
            'DRAGONQUANT_COMMISSION': ('backtest', 'commission', float),
            'DRAGONQUANT_DATA_PATH': ('data', 'data_path', str),
            'DRAGONQUANT_LOG_LEVEL': ('logging', 'level', str),
            'DRAGONQUANT_RESULTS_PATH': ('output', 'results_path', str),
            'DRAGONQUANT_CACHE_ENABLED': ('backtest', 'cache_enabled', lambda x: x.lower() == 'true'),
        }
        
        for env_var, (section, key, converter) in env_mapping.items():
            value = os.getenv(env_var)
            if value is not None:
                try:
                    converted_value = converter(value)
                    self._config[section][key] = converted_value
                    logger.debug(f"Environment variable {env_var} loaded: {converted_value}")
                except (ValueError, TypeError) as e:
                    logger.warning(f"Invalid value for {env_var}: {value} ({str(e)})")
    
    def _merge_config(self, base: Dict[str, Any], override: Dict[str, Any]):
        """深度合并配置字典"""
        for key, value in override.items():
            if key in base and isinstance(base[key], dict) and isinstance(value, dict):
                self._merge_config(base[key], value)
            else:
                base[key] = value
    
    def get(self, path: str, default: Any = None) -> Any:
        """获取配置值
        
        Args:
            path: 配置路径，如 'backtest.initial_cash'
            default: 默认值
            
        Returns:
            配置值
        """
        try:
            keys = path.split('.')
            value = self._config
            
            for key in keys:
                if isinstance(value, dict) and key in value:
                    value = value[key]
                else:
                    return default
            
            return value
            
        except Exception as e:
            logger.warning(f"Error getting config {path}: {str(e)}")
            return default
    
    def set(self, path: str, value: Any):
        """设置配置值
        
        Args:
            path: 配置路径，如 'backtest.initial_cash'
            value: 配置值
        """
        try:
            keys = path.split('.')
            config = self._config
            
            # 导航到最后一级
            for key in keys[:-1]:
                if key not in config:
                    config[key] = {}
                elif not isinstance(config[key], dict):
                    config[key] = {}
                config = config[key]
            
            # 设置值
            config[keys[-1]] = value
            logger.debug(f"Configuration set: {path} = {value}")
            
        except Exception as e:
            logger.error(f"Error setting config {path}: {str(e)}")
            raise ConfigurationError(f"Failed to set config: {str(e)}")
    
    def validate(self) -> bool:
        """验证配置有效性"""
        try:
            # 验证必要的配置项
            required_configs = [
                ('backtest.initial_cash', (int, float)),
                ('backtest.commission', (int, float)),
                ('data.data_path', str),
                ('logging.level', str)
            ]
            
            for path, expected_type in required_configs:
                value = self.get(path)
                if value is None:
                    raise ConfigurationError(f"Missing required config: {path}")
                
                if not isinstance(value, expected_type):
                    raise ConfigurationError(f"Invalid type for {path}: expected {expected_type}, got {type(value)}")
            
            # 验证数值范围
            if self.get('backtest.initial_cash') <= 0:
                raise ConfigurationError("Initial cash must be positive")
            
            if not 0 <= self.get('backtest.commission') <= 1:
                raise ConfigurationError("Commission rate must be between 0 and 1")
            
            logger.info("Configuration validation passed")
            return True
            
        except Exception as e:
            logger.error(f"Configuration validation failed: {str(e)}")
            raise ConfigurationError(f"Configuration validation failed: {str(e)}")
    
    def save_to_file(self, config_file: str):
        """保存配置到文件"""
        try:
            config_path = Path(config_file)
            config_path.parent.mkdir(parents=True, exist_ok=True)
            
            with open(config_path, 'w', encoding='utf-8') as f:
                if config_path.suffix.lower() == '.json':
                    json.dump(self._config, f, indent=2, ensure_ascii=False)
                elif config_path.suffix.lower() in ['.yaml', '.yml']:
                    yaml.dump(self._config, f, default_flow_style=False, allow_unicode=True)
                else:
                    raise ConfigurationError(f"Unsupported config file format: {config_path.suffix}")
            
            logger.info(f"Configuration saved to {config_file}")
            
        except Exception as e:
            logger.error(f"Failed to save config to {config_file}: {str(e)}")
            raise ConfigurationError(f"Failed to save config: {str(e)}")
    
    def reset_to_default(self):
        """重置为默认配置"""
        self._config = self.DEFAULT_CONFIG.copy()
        logger.info("Configuration reset to default")
    
    def get_all(self) -> Dict[str, Any]:
        """获取所有配置"""
        return self._config.copy()
    
    def update(self, config_dict: Dict[str, Any]):
        """批量更新配置"""
        try:
            self._merge_config(self._config, config_dict)
            logger.info("Configuration updated")
        except Exception as e:
            logger.error(f"Failed to update configuration: {str(e)}")
            raise ConfigurationError(f"Failed to update configuration: {str(e)}")


# 全局配置实例
_global_config: Optional[Config] = None


def get_config() -> Config:
    """获取全局配置实例"""
    global _global_config
    if _global_config is None:
        # 尝试加载默认配置文件
        default_config_files = ['./config.yaml', './config.json', './dragonquant.yaml', './dragonquant.json']
        config_file = None
        
        for file_path in default_config_files:
            if os.path.exists(file_path):
                config_file = file_path
                break
        
        _global_config = Config(config_file)
    
    return _global_config


def set_config(config: Config):
    """设置全局配置实例"""
    global _global_config
    _global_config = config


def create_default_config_file(file_path: str = './config.yaml'):
    """创建默认配置文件"""
    try:
        config = Config()
        config.save_to_file(file_path)
        logger.info(f"Default configuration file created: {file_path}")
    except Exception as e:
        logger.error(f"Failed to create default config file: {str(e)}")
        raise ConfigurationError(f"Failed to create default config file: {str(e)}")